// --------------------------------
// <copyright file="CompileEngine.cs" company="TRONSoft">
//     Copyright (c) TRONSoft.
// </copyright>
// <license>
//   This source code is subject to terms and conditions of the Microsoft
//   Public License. A copy of the license can be found in the License.html
//   file at the root of this distribution. If you cannot locate the
//   Microsoft Public License, please send an email to dlr@microsoft.com.
//   By using this source code in any fashion, you are agreeing to be bound 
//   by the terms of the Microsoft Public License. You must not remove this
//   notice, or any other, from this software.
// </license>
// <author>Tron</author>
// ---------------------------------

using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
using Microsoft.VisualBasic;
using Tronsoft.Actucal.Tables;

namespace Tronsoft.Actucal.CalculationEngineCreator
{
    /// <summary>
    /// The compile engine generates a calculation assembly based on a model.
    /// </summary>
    public class CompileEngine
    {
        private readonly CalculationModel model;
        private readonly CodeDomProvider provider;

        /// <summary>
        /// The constructor to create the instance of the compile engine for the calculation class.
        /// </summary>
        /// <param name="model">The calculation model the calculation class is based on.</param>
        public CompileEngine(CalculationModel model)
        {
            this.model = model;
            this.provider = new VBCodeProvider();
        }

        /// <summary>
        /// Creates an assembly.
        /// </summary>
        /// <returns>The assembly</returns>
        public Assembly CreateAssembly()
        {
            return CreateAssembly(string.Empty);
        }

        /// <summary>
        /// Creates an assembly.
        /// </summary>
        /// <param name="assemblyName">The name of the assembly. It can be empty.</param>
        /// <returns>The assembly</returns>
        public Assembly CreateAssembly(string assemblyName)
        {
            Trace.WriteLine("Starting running template runnable code.");

            // Create the compiler options
            CompilerParameters options = new CompilerParameters();

            // Add the referenced assemblies
            options.ReferencedAssemblies.AddRange(model.ReferencedAssemblies.ToArray());

            // If an assembly name was specified use it
            if (!string.IsNullOrEmpty(assemblyName))
            {
                options.OutputAssembly = assemblyName;
            }

            // These are vb.net specific options.
            // /optionstrict+ => option strict means that we want type safety
            // /optionexplicit+ => means that variables must be declared
            options.CompilerOptions = "/optionstrict+ /optionexplicit+";
#if DEBUG
            // In debugging mode we want the assembly as a file with debug info
            // so we can debug it.
            options.GenerateInMemory = false;
            options.IncludeDebugInformation = true;
#else
            // If we are not in debug mode we want in memory generation and no debug info
            options.GenerateInMemory = true; 
            options.IncludeDebugInformation = false;

            // Set the level at which the compiler 
            // should start displaying warnings.
            options.WarningLevel = 3;

            // Set whether to treat all warnings as errors.
            options.TreatWarningsAsErrors = false;

            // Set compiler argument to optimize output.
            options.CompilerOptions += " /optimize /removeintchecks";
#endif

            // Set a temporary files collection.
            // The TempFileCollection stores the temporary files
            // generated during a build in the current directory,
            // and deletes them after compilation.
            options.TempFiles = new TempFileCollection(".", false);

            // Add the resources data, but first check if we can add it
            if (this.provider.Supports(GeneratorSupport.Resources))
            {
                // Get a temp file for the resource file
                string defaultResource = Path.GetTempFileName();

                // Add it to the temp files of the compiler so it is delete when we are done
                options.TempFiles.AddFile(defaultResource, false);

                // Create the resource file.
                using (ResourceWriter rw = new ResourceWriter(defaultResource))
                {
                    // Create a stream to hold the resource data and write the tables data
                    using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
                    {
                        TableManager.Instance.WriteXml(writer);
                        writer.Flush();

                        // add the tables data as a resource 
                        rw.AddResource(Resources.TABLES_DATA, writer.GetStringBuilder().ToString());
                    }
                }
            }

            // Create the assembly from the source file
            CompilerResults cr =
                provider.CompileAssemblyFromSource(options, new string[] {model.Source});

            // Check for error and collect them if there are any.
            if (cr.Errors.Count > 0)
            {
                StringBuilder sb = new StringBuilder();

                for (int i = 0; i < cr.Output.Count; i++)
                {
                    Debug.WriteLine(cr.Output[i]);
                    sb.AppendLine(cr.Output[i]);
                }
                for (int i = 0; i < cr.Errors.Count; i++)
                {
                    string err = String.Format("{0}: {1}", i, cr.Output[i]);
                    Debug.WriteLine(err);
                    sb.AppendLine(err);
                }

                // The errors are throw
                throw new CalculationException(sb.ToString());
            }

            return cr.CompiledAssembly;
        }

        /// <summary>
        /// Creates an instance of the calculation class.
        /// </summary>
        /// <returns>an instance of the calculation class.</returns>
        public object CreateInstanceCalculationClass()
        {
            // Get the type of the calculation class
            Type rt = CreateAssembly().GetType(model.FULLY_QUALIFIED_CALCULATION_ENGINE_CLASSNAME);
            if (null == rt)
                throw new CalculationException("Unable to create the assembly.");

            // return an instatiated object
            return Activator.CreateInstance(rt, model.CalculationParameters);
        }
    }
}